'use strict'

const { is_pair, car, cdr, to_string, cons } = require("./lang");
const { error, is_symbol } = require("./lang");
const { is_number } = require("./lang");
const { list } = require('./list')

const PLUS = Symbol('+')

const PRODUCT = Symbol('*')

const POWER = Symbol('**')

/**
 * 判断 v 是否是变量
 */
const is_variable = (v) => is_symbol(v)

/**
 * 判断 v1、v2 是否是同一个变量
 */
const is_same_variable = (v1, v2) => is_variable(v1) && is_variable(v2) && v1 === v2

const prefix_implementation = {
  // 判断表达式是否是和式
  is_sum : (exp) => is_pair(exp) && car(exp) === PLUS,

  // 被加数，和式里的第二个元素
  addend : (exp) => car(cdr(exp)),

  // 加数，和式里的第三个及其之后的元素
  augend : (exp) => {
    const t = cdr(cdr(exp))
    return cdr(t) === null ? car(t) : cons(PLUS, t)
  },

  // 构造和式
  make_sum : (a1, a2) =>
      is_number(a1) && a1 === 0 ? a2
    : is_number(a2) && a2 === 0 ? a1
    : is_number(a1) && is_number(a2) ? a1 + a2
    : prefix_implementation.is_sum(a2) ? cons(PLUS, cons(a1, cdr(a2)))
    : list(PLUS, a1, a2),

  // 是否是乘式
  is_product : (exp) => is_pair(exp) && car(exp) === PRODUCT,

  // 构造乘式
  make_product : (m1, m2) =>
      is_number(m1) && m1 === 1 ? m2
    : is_number(m1) && m1 === 0 ? 0
    : is_number(m2) && m2 === 1 ? m1
    : is_number(m2) && m2 === 0 ? 0
    : is_number(m1) && is_number(m2) ? m1 * m2
    : prefix_implementation.is_product(m2) ? cons(PRODUCT, cons(m1, cdr(m2)))
    : list(PRODUCT, m1, m2),

  // 被乘数，乘式里的第二个元素
  multiplier : (exp) => car(cdr(exp)),

  // 乘数，乘式里的第三个元素
  multiplicand : (exp) => {
    const t = cdr(cdr(exp))
    return cdr(t) === null ? car(t) : cons(PRODUCT, t)
  },

  // 是否是幂式
  is_exponentiation : (exp) => is_pair(exp) && car(exp) === POWER,

  // 构造幂式
  make_exponentiation : (b, e) =>
      is_number(e) && e === 0 ? 1
    : is_number(e) && e === 1 ? e
    : list(POWER, b, e),

  // 幂式底数，幂式里的第二个元素
  base : (exp) => car(cdr(exp)),

  // 幂式指数，幂式里的第三个元素
  exponent : (exp) => car(cdr(cdr(exp)))
}

const implement_deriv = ({
  is_sum, make_sum, addend, augend,
  is_product, make_product, multiplier, multiplicand,
  is_exponentiation, make_exponentiation, base, exponent
}) =>  {
  const deriv = (exp, v) => (
    is_number(exp)
      ? 0
      : is_variable(exp) ? is_same_variable(exp, v) ? 1 : 0
      : is_sum(exp) ? (
          make_sum(deriv(addend(exp), v), deriv(augend(exp), v))
        )
      : is_product(exp) ? (
          make_sum(
            make_product(multiplier(exp), deriv(multiplicand(exp), v)),
            make_product(multiplicand(exp), deriv(multiplier(exp), v))
          )
        )
      : is_exponentiation(exp) ? (
          make_product(
            exponent(exp),
            make_exponentiation(base(exp), make_sum(exponent(exp), -1))
          )
        )
      : error("unknow expression type")
  );
  return deriv
}

const deriv = implement_deriv(prefix_implementation)

const x = Symbol('x')
const y = Symbol('y')

const display = (it) => console.log(to_string(it))

display(deriv(list(PLUS, x, 3), x))
display(deriv(list(PRODUCT, x, 3), x))
display(deriv(list(PRODUCT, x, x), x))
display(deriv(list(PLUS, list(PRODUCT, x, x), 3), x))
display(deriv(list(PRODUCT, x, y), x))
display(deriv(list(POWER, x, y), x))
display(deriv(list(PLUS, x, 3, y, 4), x))
display(deriv(list(PLUS, x, list(PRODUCT, x, 2), list(POWER, x, x), 6), x))
display(deriv(list(PRODUCT, x, list(PLUS, x, 1), list(PLUS, list(PRODUCT, 3, x), 2)), x))

